﻿using System;
using System.Collections.Generic;
using PacMan3D.Utils;
using Tao.OpenGl;
using Tao.FreeGlut;
using PacMan3D.Sounds;
using PacMan3D.Objects.Models;
using SalmonViewer.Loader3DS;

namespace PacMan3D.Objects
{
    public class PacMan : IRenderable, ICollidable
    {
        public bool IsEasyMode { get; private set; }
        public bool IsBorning { get; private set; }
        public bool IsAlive {get; private set;}
        private bool IsVisible;
        private bool IsStoped;


        private float PSize;
        private float RadiusSize;
        
        private float NormalSpeed;
        private float EasyModeSpeed;
        private float ActualSpeed;
        
        public Point3D Position {get; private set;}
        public DirectionType DirectionType { get; private set; }

        public Vector Direction { get; private set; }

        private Point3D BornPosition;
        private Point3D LastPosition;

        private int Slices = 25;
        private int Stacks = 25;

        /// 
        /// PacMan Material 
        /// 
        private float[] Specular = { 0.9f, 0.9f, 0.9f,  1.0f };
        private int Shininess = 100;
        
        public int Lifes { get; private set; }
        public int Points { get; private set; }

        public float AnimePacManScale {get; private set;}

        private float PacManAlpha;

        /// 
        /// The 3DS PacMan Model
        /// 
        private Model PacMan3DSModel;

        ///
        /// PacMan OpenGL Model
        private PacManModel PacManOpenGLModel;

        private bool Model3DS;

        public PacMan(bool model3ds, Point3D position, float radius)
        {
            this.Model3DS = model3ds;

            this.PSize = radius / 2.0f;
            this.RadiusSize = this.PSize / 2.0f;
            this.NormalSpeed = this.RadiusSize / 2.0f;
            this.EasyModeSpeed = this.NormalSpeed / 2.0f;

            this.Position = position;
            this.Position.Y = this.RadiusSize;
            this.BornPosition = this.Position.Clone();

            this.PacManAlpha = 0.6f;

            this.Lifes = 3;
            this.Points = 0;

            if (this.Model3DS)
            {
                ///
                /// Load the PacMan 3DS Model
                /// 
                this.PacMan3DSModel = new ThreeDSFile(Constants.RES_MODEL_PACMAN).Model;
            }
            else
            {
                ///
                /// PacMan OpenGL Model
                /// 
                this.PacManOpenGLModel = new PacManModel(this.RadiusSize, this.Slices, this.Stacks, this.PacManAlpha);
            }

            BuildPacMan();
        }

        private void BuildPacMan()
        {
            this.AnimePacManScale = 1.0f;
            this.Position = this.BornPosition.Clone();
            this.LastPosition = this.BornPosition.Clone();
            this.Direction = new Vector(0.0f, 0.0f, 0.0f);
            UpdateDirection(DirectionType.Left);
            this.IsAlive = true;
            this.IsBorning = true;
            this.IsEasyMode = false;
            this.IsStoped = true;
            this.UpdateActualSpeed();
        }

        #region IRendable Members
        
        public override void DrawObject()
        {
            if (!this.IsVisible)
                return;

            if (this.IsBorning)
            {
                Gl.glEnable(Gl.GL_BLEND);                
                Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
            }

            if (!this.Model3DS)
                Gl.glEnable(Gl.GL_LIGHTING);

            Gl.glPushMatrix();
                // para deslocar o PacMan para o sítio onde ele está posicionado
                Gl.glTranslatef(this.Position.X, this.Position.Y, this.Position.Z);
                
                // para colocar o PacMan na direção respectiva
                Gl.glRotatef((float)this.DirectionType, 0.0f, 1.0f, 0.0f);

                Gl.glScalef(this.AnimePacManScale, this.AnimePacManScale, this.AnimePacManScale);

                if (this.Model3DS)
                {
                    Gl.glColor4f(1.0f, 1.0f, 0.0f, 1.0f);

                    Gl.glScalef(3.0f, 3.0f, 3.0f);

                    Gl.glRotatef(180.0f, 0.0f, 1.0f, 0.0f);

                    ///
                    /// Render the PacMan 3DS Model
                    ///
                    this.PacMan3DSModel.Render();
                }
                else
                {
                    // define as propriedades de reflectância do material
                    Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_SPECULAR, this.Specular);

                    // define as propriedades de brilho do material
                    Gl.glMateriali(Gl.GL_FRONT, Gl.GL_SHININESS, this.Shininess);

                    ///
                    /// Render the PacMan OpenGL Model
                    ///
                    this.PacManOpenGLModel.DrawObject();
                }

            Gl.glPopMatrix();

            Gl.glDisable(Gl.GL_LIGHTING); 
            Gl.glDisable(Gl.GL_BLEND); 
        }

        /// <summary>
        /// Método que desenha o vector da direcção do PacMan.
        /// </summary>
        private void DrawDirectionVetor()
        {
            Gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
            Gl.glPushMatrix();   
                Gl.glBegin(Gl.GL_LINES);
                    Gl.glVertex3f(this.Position.X, this.Size, this.Position.Z);
                    Gl.glVertex3f(this.Position.X + 1.0f * this.Direction.X, this.Size, this.Position.Z + 1.0f * this.Direction.Z);
                Gl.glEnd();
            Gl.glPopMatrix();
        }

        #endregion

        #region Amine PacMan 
        public bool IsDyingAnimeFinished()
        {
            return (this.AnimePacManScale <= 0.0f);
        }

        public void PlayDyingAnime()
        {
            this.AnimePacManScale -= 0.025f;

            if ((this.AnimePacManScale <= 0.0f) && (this.Lifes >= 1))
                BuildPacMan();
        }

        private int Age;
        private int AgeLimit = 80;
        public void IncreaseAge()
        {
            this.Age++;

            if (this.Age > this.AgeLimit)
            {
                this.Age = 0;
                this.IsBorning = false;
            }
        }

        #endregion

        public void ChangeDirection(DirectionType direction)
        {
            if (!this.IsAlive)
                return;

            if (this.IsStoped) {
                this.IsStoped = false;
                this.ChangeMode();
            }

            if (this.DirectionType != direction)
                UpdateDirection(direction);

            UpdatePosition();
        }

        public void UpdatePosition()
        {
            this.LastPosition = this.Position.Clone();
            this.Position.X += this.Direction.X * this.ActualSpeed;
            this.Position.Z += this.Direction.Z * this.ActualSpeed;
        }

        public void ChangeMode()
        {
            this.IsEasyMode = !this.IsEasyMode;
            this.UpdateActualSpeed();
        }

        public void DisableEasyMode()
        {
            this.IsEasyMode = false;
            this.UpdateActualSpeed();
        }

        private void UpdateActualSpeed()
        {
            this.ActualSpeed = ((this.IsEasyMode) ? this.EasyModeSpeed : this.NormalSpeed);
        }

        public void MoveBack()
        {
            this.Position = this.LastPosition;
        }

        private void UpdateDirection(DirectionType direction)
        {
            this.DirectionType = direction;
            switch (direction)
            {
                case DirectionType.Up:
                    this.Direction.UpdateOnlyZ(-1.0f);
                    break;
                case DirectionType.Down:
                    this.Direction.UpdateOnlyZ(1.0f);
                    break;
                case DirectionType.Left:
                    this.Direction.UpdateOnlyX(-1.0f);
                    break;
                case DirectionType.Right:
                    this.Direction.UpdateOnlyX(1.0f);
                    break;
            }
        }

        public Point3D GetPositionAhead()
        {
            return new Point3D((float)(this.Position.X + this.Direction.X * this.Size), 0.0f, 
                ((float)this.Position.Z + this.Direction.Z * this.Size));
        }

        public bool Collides(ICollidable obj)
        {
            return (Math.Abs(this.Position.X - obj.Position.X) < (this.Size + obj.Size)
                && Math.Abs(this.Position.Z - obj.Position.Z) < (this.Size + obj.Size));
        }

        #region ICollidable Members

        public float Size
        {
            get { return this.RadiusSize; }
        }

        #endregion

        public void EatFood()
        {
            this.Points += Constants.POINTS_FOOD;
        }

        public void EatBigFood()
        {
            this.Points += Constants.POINTS_BIG_FOOD;
        }

        public void GetLife()
        {
            this.Lifes++;
        }

        public void LooseLife()
        {
            this.IsAlive = false;
            this.Lifes--;
        }

        public void HidePacMan()
        {
            this.IsVisible = false;
        }

        public void ShowPacMan()
        {
            this.IsVisible = true;
        }
    }

    public enum DirectionType
    {
        Left = 0, 
        Down = 90,
        Right = 180, 
        Up = 270,
        None
    }
}
